package com.leyou.search.service;

import com.fasterxml.jackson.core.type.TypeReference;
import com.leyou.common.enums.ExceptionEnum;
import com.leyou.common.exception.LyException;
import com.leyou.common.utils.BeanHelper;
import com.leyou.common.utils.JsonUtils;
import com.leyou.common.vo.PageResult;
import com.leyou.item.client.ItemClient;
import com.leyou.item.dto.*;
import com.leyou.search.dto.GoodsDTO;
import com.leyou.search.dto.SearchRequest;
import com.leyou.search.entity.Goods;
import com.leyou.search.repository.GoodsRepository;
import org.apache.commons.lang.StringUtils;

import org.elasticsearch.index.query.*;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.query.FetchSourceFilter;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.util.*;
import java.util.stream.Collectors;

@Service
public class SearchService {

    @Autowired
    private ItemClient itemClient;
    //spring提供的搜索模板, 支持复杂查询
    @Autowired
    private ElasticsearchTemplate esTemplate;

    @Autowired
    private GoodsRepository goodsRepository;



    /**
     * 创建Goods对象
     * @param spuDTO    spuDTO对象
     * @return          Goods对象
     */
    public Goods buildGoods(SpuDTO spuDTO){
        //0.spu的id
        Long spuId = spuDTO.getId();

        //1.spu下所有sku集合
        List<SkuDTO> skuDTOList = itemClient.querySkuListBySpuId(spuId);
        List<Map<String,Object>> skuList = new ArrayList<>();
        for (SkuDTO skuDTO : skuDTOList) {
            Map<String, Object> map = new HashMap<>();
            map.put("id",skuDTO.getId());
//            map.put("image",skuDTO.getImages().split(",")[0]);
            map.put("image", StringUtils.substringBefore(skuDTO.getImages(),","));  //工具类截取(有判空)
            map.put("price",skuDTO.getPrice());
            map.put("title",skuDTO.getTitle());
            skuList.add(map);
        }
        //2.商品的价格集合
        Set<Long> price = skuDTOList.stream().map(SkuDTO::getPrice).collect(Collectors.toSet());

        //3.搜索字段all(商品名 / 分类名 / 品牌名)
        //3.1分类名称
        List<CategoryDTO> categoryDTOList = itemClient.queryCategoryByIds(spuDTO.getCategoryIds());
        String categoryName = categoryDTOList.stream().map(CategoryDTO::getName).collect(Collectors.joining()); //把全部分类名称都拼接起来, 中间没有拼接符(例:通讯手机华为)
        //3.2品牌名称
        BrandDTO brandDTO = itemClient.queryBrandById(spuDTO.getBrandId());
        String brandName = brandDTO.getName();
        //3.3拼接起来(商品名 / 分类名 / 品牌名)
        String all = spuDTO.getName() + categoryName + brandName;

        //4.规格参数集合
        HashMap<String, Object> specs = new HashMap<>();
        //4.1 获取规格参数key: 规格参数名作为key
        List<SpecParamDTO> specParamDTOS = itemClient.querSpecParam(null, spuDTO.getCid3(), true);
        //4.2 查出SpuDetail: 值来自于spuDetail中的通用规格参数和特有规格参数
        SpuDetailDTO spuDetailDTO = itemClient.querySpuDetailBySpuId(spuId);
        //获取通用规格参数
        Map<Long, Object> genericSpecMap = JsonUtils.toMap(spuDetailDTO.getGenericSpec(), Long.class, Object.class);
        Map<Long, List<String>> specialSpecMap = JsonUtils.nativeRead(spuDetailDTO.getSpecialSpec(), new TypeReference<Map<Long, List<String>>>() {
        });

        //4.3 把值存入specs中
        for (SpecParamDTO spd : specParamDTOS) {
            //规格参数名称
            String key = spd.getName();

            Object value;
            //判断是否为通用属性
            if (spd.getGeneric()){
                //通用属性
                value = genericSpecMap.get(spd.getId());
            } else {
                //特有属性
                value = specialSpecMap.get(spd.getId());
            }

            //处理数字类型的区间规格参数(价格,英寸等...)
            if (spd.getNumeric()){
                value = chooseSegment(value,spd);
            }

            //把key/value都存到specs集合中
            specs.put(key,value);
        }

        //封装Goods对象
        Goods goods = new Goods();
        goods.setSkus(JsonUtils.toString(skuList));    //spu下所有sku集合
        goods.setAll(all);     // 搜索字段
        goods.setPrice(price); // 商品价格
        goods.setSpecs(specs); // 商品规格参数
        goods.setId(spuId);
        goods.setSubTitle(spuDTO.getSubTitle());
        goods.setBrandId(spuDTO.getBrandId());
        goods.setCategoryId(spuDTO.getCid3());
        goods.setCreateTime(spuDTO.getCreateTime().getTime());

        return goods;
    }


    /**
     * 分割价格/规格抽取出来的方法(严谨性比较好)
     * @param value
     * @param p
     * @return
     */
    private String chooseSegment(Object value, SpecParamDTO p) {
        if (value == null || StringUtils.isBlank(value.toString())) {
            return "其它";
        }
        double val = parseDouble(value.toString());
        String result = "其它";
        // 保存数值段
        for (String segment : p.getSegments().split(",")) {
            String[] segs = segment.split("-");
            // 获取数值范围
            double begin = parseDouble(segs[0]);
            double end = Double.MAX_VALUE;
            if (segs.length == 2) {
                end = parseDouble(segs[1]);
            }
            // 判断是否在范围内
            if (val >= begin && val < end) {
                if (segs.length == 1) {
                    result = segs[0] + p.getUnit() + "以上";
                } else if (begin == 0) {
                    result = segs[1] + p.getUnit() + "以下";
                } else {
                    result = segment + p.getUnit();
                }
                break;
            }
        }
        return result;
    }
    private double parseDouble(String str) {
        try {
            return Double.parseDouble(str);
        } catch (Exception e) {
            return 0;
        }
    }


    /**
     * 分页基础搜索商品数据
     * @param request
     * @return
     */
    public PageResult<GoodsDTO> search(SearchRequest request) {
        //0.判断关键字
        String key = request.getKey();
        if (StringUtils.isBlank(key)){
            throw new LyException(ExceptionEnum.INVALID_PARAM_ERROR);
        }

        //1.构建搜索
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        //2.1.拼接搜索条件
        queryBuilder.withQuery(builderSearchKey(request));
        //2.2.分页条件
        queryBuilder.withPageable(PageRequest.of(request.getPage() - 1,request.getSize())); //默认从0开始,要-1
        //2.3.指定获取内容字段
        queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{"id","subTitle","skus"},null));

        //3.搜索结果
        AggregatedPage<Goods> aggregatedPage = esTemplate.queryForPage(queryBuilder.build(), Goods.class);
        //4.解析结果
        //4.1.总记录数
        long total = aggregatedPage.getTotalElements();
        //4.2.总页数
        int totalPages = aggregatedPage.getTotalPages();
        //4.3当前页数据
        List<Goods> content = aggregatedPage.getContent();
        List<GoodsDTO> goodsDTOList = BeanHelper.copyWithCollection(content, GoodsDTO.class);

        //5.封装数据并返回
        return new PageResult<>(goodsDTOList,total,totalPages);
    }

    /**
     * 查出过滤项
     * @param searchRequest
     * @return
     */
    public Map<String, List<?>> filter(SearchRequest searchRequest) {
        //定义一个新的LinkedHashMap
        Map<String, List<?>> filterPramMap = new LinkedHashMap<>();

        //0.判断关键字
        String key = searchRequest.getKey();
        if (StringUtils.isBlank(key)){
            throw new LyException(ExceptionEnum.INVALID_PARAM_ERROR);
        }

        //1.构建搜索
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        //2.1.拼接搜索条件
        queryBuilder.withQuery(builderSearchKey(searchRequest));
        //2.2.分页条件: 如果指定size为0, SpringDataElasticSearch就不会去查询, 所以至少写个1
        queryBuilder.withPageable(PageRequest.of(0,1));
        //2.3.指定获取内容字段
        queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{""},null));

        //====================================================================================
        //============================开始添加聚合条件: 分类&品牌=============================
        //====================================================================================
        //2.4.添加聚合条件
        //--->分类
        String categoryAgg = "categoryAgg";
        queryBuilder.addAggregation(
                AggregationBuilders.terms(categoryAgg)
                        .field("categoryId")
                        .size(10)
        );
        //--->品牌
        String brandAgg = "brandAgg";
        queryBuilder.addAggregation(
                AggregationBuilders.terms(brandAgg)
                        .field("brandId")
                        .size(10)
        );

        //====================================================================================
        //=================================处理搜索结果集=====================================
        //====================================================================================

        //3.搜索结果
        AggregatedPage<Goods> aggregatedPage = esTemplate.queryForPage(queryBuilder.build(), Goods.class);
        Aggregations aggregations = aggregatedPage.getAggregations();

        //取出集合
        Terms categoryTerm = aggregations.get(categoryAgg); //--->分类
        Terms brandTerm = aggregations.get(brandAgg);       //--->品牌
        //处理聚合
        List<Long> cids = handleCategoryFilter(categoryTerm, filterPramMap);//--->分类
        handleBrandFilter(brandTerm,filterPramMap);      //---->品牌

        //====================================================================================
        //============处理过滤规格参数(根据第一次的聚合id集合去查询规格参数,最后再聚合)=======
        //====================================================================================
        handleSpecParamFilter(cids,searchRequest,filterPramMap);

        return filterPramMap;
    }

    /**
     * 处理分类集合下的所有规格参数
     * @param cids              分类集合id
     * @param searchRequest
     * @param filterPramMap
     */
    private void handleSpecParamFilter(List<Long> cids,SearchRequest searchRequest,Map<String, List<?>> filterPramMap) {
        for (Long cid : cids) {
            //查询出规格参数
            List<SpecParamDTO> specParamDTOList = itemClient.querSpecParam(null, cid, true);
            if (!CollectionUtils.isEmpty(specParamDTOList)){
                //开始添加
                //1.构建搜索
                NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
                //2.1.拼接搜索条件
                queryBuilder.withQuery(builderSearchKey(searchRequest));
                //2.2.分页条件: 如果指定size为0, SpringDataElasticSearch就不会去查询, 所以至少写个1
                queryBuilder.withPageable(PageRequest.of(0,1));
                //2.3.指定获取内容字段
                queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{""},null));

                //====================================================================================
                //============================开始添加聚合条件: 规格参数==============================
                //====================================================================================
                for (SpecParamDTO specParamDTO : specParamDTOList) {
                    //聚合名称
                    String aggName = specParamDTO.getName();
                    //field名称
                    String fieldName = "specs." + aggName;
                    queryBuilder.addAggregation(
                            AggregationBuilders.terms(aggName)
                                    .field(fieldName));
                }
                AggregatedPage<Goods> aggregatedPage = esTemplate.queryForPage(queryBuilder.build(), Goods.class);
                Aggregations aggregations = aggregatedPage.getAggregations();

                for (SpecParamDTO spd : specParamDTOList) {
                    String aggName = spd.getName();
                    Terms terms = aggregations.get(aggName);
                    List<String> specParm = terms.getBuckets()
                            .stream()
                            .map(Terms.Bucket::getKeyAsString)
                            .filter(StringUtils::isNotBlank)
                            .collect(Collectors.toList());
                    //得到所有的参数后,放入map中
                    filterPramMap.put(aggName,specParm);
                }
            }
        }
    }


    /**
     * 处理分类的聚合
     */
    private List<Long> handleCategoryFilter(Terms categoryTerm, Map<String, List<?>> filterPramMap) {
        List<? extends Terms.Bucket> buckets = categoryTerm.getBuckets();
        //获取到所有分类的id
        List<Long> cIds = new ArrayList<>();
        for (Terms.Bucket bucket : buckets) {
            //取出key
            Long key = (Long)bucket.getKeyAsNumber();
            cIds.add(key);
        }
        List<CategoryDTO> categoryDTOList = itemClient.queryCategoryByIds(cIds);
        filterPramMap.put("分类",categoryDTOList);

        //返回分类的id集合, 用于查询规格参数(可搜索)
        return cIds;
    }

    /**
     * 处理品牌的聚合
     */
    private void handleBrandFilter(Terms brandTerm, Map<String, List<?>> filterPramMap) {
        //获取品牌id
        List<Long> brandIds = brandTerm.getBuckets()
                .stream()
                .map(Terms.Bucket::getKeyAsNumber)
                .map(Number::longValue)
                .collect(Collectors.toList());
        //查询品牌的列表
        List<BrandDTO> brandDTOS = itemClient.queryBrandByIds(brandIds);

        //存入map中
        filterPramMap.put("品牌",brandDTOS);
    }

    /*
     抽取过滤条件
     */
    private QueryBuilder builderSearchKey(SearchRequest searchRequest){
        //改造成bool查询
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
        //1.拼接搜索关键字(必须的)
        MatchQueryBuilder matchQuery = QueryBuilders.matchQuery("all", searchRequest.getKey()).operator(Operator.AND);
        boolQuery.must(matchQuery);
        //2.拼接过滤参数
        searchRequest.getFilterMap().entrySet().forEach(entry -> {
            String key = entry.getKey();
            Object value = entry.getValue();

            String fieldName = "";
            if ("品牌".equals(key)){
                fieldName = "brandId";
            } else if ("分类".equals(key)){
                fieldName = "categoryId";
            } else {
                fieldName = "specs." + key;
            }
            boolQuery.filter(QueryBuilders.termQuery(fieldName,value));
        });



        return boolQuery;
    }

    /**
     * 创建ES索引 (商品上架)
     * @param spuId
     */
    public void saveGoods(Long spuId) {
        //获取spu数据
        SpuDTO spuDTO = itemClient.querySpuById(spuId);
        //构建Goods对象
        Goods goods = buildGoods(spuDTO);
        //更新索引库
        goodsRepository.save(goods);
    }

    /**
     * 删除ES索引库(商品下架)
     * @param spuId
     */
    public void deleteGoods(Long spuId){
        goodsRepository.deleteById(spuId);
    }

}
